package com.isyscore.os.driver;

import com.isyscore.os.driver.core.driver.*;
import com.isyscore.os.driver.core.enums.AttributeOperationEnum;
import com.isyscore.os.driver.core.enums.ItemTypeEnum;
import com.isyscore.os.driver.core.utils.UtilFunctions;
import org.apache.commons.lang3.math.NumberUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.xml.bind.DatatypeConverter;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @author stw
 */
public class ModbusRtuOverTcp extends DeviceDriverBase {
    private final static Logger log = LoggerFactory.getLogger(ModbusRtuOverTcp.class);
    private final static int INVALIDATE_FUNCTION = -1;
    private final static String TYPE_FLOAT = "float";
    private final int[] nBitMask = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};
    /**
     * ModbusRTUAccess驱动所能提供的属性能力
     */
    public DriverAttribute[] attributes;

    /**
     * key:devId
     */
    private final Map<String, DeviceInfo> deviceInfoMap = new HashMap<>();

    private final TcpHandler tcpHandler = new TcpHandler();

    public ModbusRtuOverTcp() {
        super("960a75e396f45049a434f97f7a0dc0",
                "指令集",
                "标准ModbusRTU转TCP设备",
                "2.0",
                "标准ModbusRTU转TCP驱动Server",
                "指令集",
                false,
                "标准ModbusRTU转TCP驱动Server",
                false);
    }

    @Override
    public DriverFunctions initFunctions() {
        DriverFunctions abilities = new DriverFunctions();
        abilities.setIsTsl(false);
        //属性
        attributes = new DriverAttribute[]{
                new DriverAttribute("00000", "00000(读写线圈)", "寄存器地址"),
                new DriverAttribute("10000", "10000(只读线圈)", "寄存器地址"),
                new DriverAttribute("30000", "30000(只读寄存器)", "寄存器地址;缩放因子"),
                new DriverAttribute("40000", "40000(读写寄存器)", "寄存器地址;缩放因子"),
                new DriverAttribute("80000", "80000(只读扩展寄存器)", "寄存器地址;缩放因子;数据类型"),
                new DriverAttribute("90000", "90000(读写扩展寄存器)", "寄存器地址;缩放因子;数据类型"),
                new DriverAttribute("A0000", "A0000(只读扩展寄存器-高低位互换)", "寄存器地址;缩放因子;数据类型"),
                new DriverAttribute("B0000", "B0000(读写扩展寄存器-高低位互换)", "寄存器地址;缩放因子;数据类型")
        };

        abilities.setDriverAttributes(Arrays.asList(attributes));
        return abilities;

    }

    @Override
    public List<ConnectParam> initConnectParams() {
        return Collections.singletonList(new ConnectParam("port", "打开的server端口"));
    }

    @Override
    public int initial(InitialParam initialParam) {
        for (DeviceInfo deviceInfo : initialParam.getDeviceInfoList()) {
            deviceInfoMap.put(deviceInfo.getDevId(), deviceInfo);
        }
        String port = initialParam.getConnectParams().get(0).getValue();
        tcpHandler.startServer(tcpHandler, Integer.parseInt(port));
        //睡眠10秒等待客户端连接
        try {
            TimeUnit.SECONDS.sleep(10);
        } catch (InterruptedException e) {
            log.warn("initial被打断");
        }
        return 0;
    }

    @Override
    public int unInitial() {
        tcpHandler.close();
        return 0;
    }

    @Override
    public DriverResult checkConnectParams(List<ConnectParam> connectParams) {
        String port = connectParams.get(0).getValue();
        if (!NumberUtils.isDigits(port)) {
            log.error("port必须为1——65535的数字");
            return new DriverResult(-1, port + ":port必须为1——65535的数字");
        }
        return DriverResult.SUCCESS;
    }

    @Override
    public DriverResult checkFunction(ItemTypeEnum functionType, String functionName, String functionParam) {
        if (functionType.equals(ItemTypeEnum.ATTRIBUTE)){
            return checkAttribute(functionName, functionParam);
        }
        return new DriverResult(-1, "驱动能力只有属性");
    }

    public DriverResult checkAttribute(String attributeName, String regAddress) {
        int index = getFuncIndexByName(ItemTypeEnum.ATTRIBUTE, attributeName);
        if (index == -1) {
            return new DriverResult(-1, "不能存在此属性" + attributeName);
        }
        String[] split = regAddress.split(";");

        if (!NumberUtils.isDigits(split[0])) {
            return new DriverResult(-1, "地址必须为0-65535数值");
        }
        int nValue = Integer.parseInt(split[0]);
        if (nValue < 0 || nValue > 65535) {
            return new DriverResult(-1, "地址必须为0-65535数值");
        }
        if (split.length > 1 && !NumberUtils.isParsable(split[1])) {
            return new DriverResult(-1, "缩放因子必须为数字");
        }
        if (split.length > 2 && !TYPE_FLOAT.equals(split[2])) {
            return new DriverResult(-1, "只支持数据类型：float");
        }
        log.info("属性地址检查通过 {} {}", attributeName, regAddress);

        return DriverResult.SUCCESS;
    }

    @Override
    public DriverResult sortAttributeAction(List<TslAttributeAction> attributeActions) {
        //排序
        attributeActions.sort((o1, o2) -> {
            int compare = Integer.compare(o1.getDevId().hashCode(), o2.getDevId().hashCode());
            if (compare == 0) {
                compare = Integer.compare(o1.getDriverFunctionName().hashCode(), o2.getDriverFunctionName().hashCode());
                if (compare == 0) {
                    compare = Integer.compare(Integer.parseInt(o1.getDriverFunctionParam().split(";")[0]),
                            Integer.parseInt(o2.getDriverFunctionParam().split(";")[0]));
                }
            }
            return compare;
        });
        return DriverResult.SUCCESS;
    }

    @Override
    public boolean canPackage(TupleFrame frame, TslAttributeAction attr) {
        if (AttributeOperationEnum.READ == frame.getOperation()) {
            String deviceIdentifier = frame.getAttributeActions().get(0).getDevId();
            String registerType = frame.getAttributeActions().get(0).getDriverFunctionName();
            if (frame.getAttributeActions().size() >= 64) {
                // 一帧最多64个属性点
                return false;
            }
            if (!deviceIdentifier.equals(attr.getDevId())) {
                // 设备地址不同不允许打包
                return false;
            }
            if (!registerType.equals(attr.getDriverFunctionName())) {
                // 寄存器类型不同不允许打包
                return false;
            }
            // 寄存器地址不连续不允许打包
            List<TslAttributeAction> attrsInFrame = frame.getAttributeActions();
            TslAttributeAction attrInFrame = attrsInFrame.get(attrsInFrame.size() - 1);
            int address1 = Integer.parseInt(attrInFrame.getDriverFunctionParam().split(";")[0]);
            int address2 = Integer.parseInt(attr.getDriverFunctionParam().split(";")[0]);
            int regIndex = getFuncIndexByName(ItemTypeEnum.ATTRIBUTE, registerType);
            // 前寄存器地址间隔超过1则认为不连续
            // 后续寄存器地址间隔超过2则认为不连续
            int interval = regIndex < 4 ? 1 : 2;
            return address2 - address1 <= interval;
        } else {
            // 写操作限制单个写入
            return false;
        }
    }

    @Override
    public DriverResult accessAttribute(TupleFrame tupleFrame) {
        List<TslAttributeAction> actions = tupleFrame.getAttributeActions();
        if (actions.size() <= 0) {
            log.error("无效的action");
            return DriverResult.FAILED;
        }

        // 在canPackage中，已经保证同一帧中的属性点位都属于同一个寄存器
        // 直接把第一个action的属性类型和属性类型当成整个帧的属性类型和操作类型
        String devId = actions.get(0).getDevId();
        String[] splits = deviceInfoMap.get(devId).getExtra().split(";");
        String ipAddr = splits[0];
        String deviceAddress = splits[1];
        String registerType = actions.get(0).getDriverFunctionName();
        String identifier = actions.get(0).getIdentifier();
        AttributeOperationEnum operation = tupleFrame.getOperation();
        log.debug("devId:{} ipAddr:{} identifier:{} accessAttribute", devId, ipAddr, identifier);
        int index = getFuncIndexByName(ItemTypeEnum.ATTRIBUTE, registerType);
        if (index == -1) {
            return new DriverResult(-1, "不能存在此属性：" + registerType);
        }

        int nFunCode = GetFunctionCode(registerType, operation);
        if (nFunCode == INVALIDATE_FUNCTION) {
            return new DriverResult(-1, "未找到合法的功能码");
        }

        // 遍历action数组，获取其最大最小范围
        int nMinAddr = 0;
        int nMaxAddr = 0;
        boolean bFirstEnter = true;
        for (TslAttributeAction action : actions) {
            int nAddr = Integer.parseInt(action.getDriverFunctionParam().split(";")[0]);
            if (bFirstEnter) {
                bFirstEnter = false;
                nMinAddr = nAddr;
                nMaxAddr = nAddr;
            }
            nMinAddr = Math.min(nAddr, nMinAddr);
            nMaxAddr = Math.max(nAddr, nMaxAddr);
        }

        int nSlaveId = Integer.parseInt(deviceAddress);          // 设备地址/设备ID/从机SlaveId
        int nStart = nMinAddr;              // 寄存器的偏移位置
        int nLen = nMaxAddr - nMinAddr + 1; // 本次读取总长度
        if (index >= 4 && index <= 7) {
            // 扩展寄存器都是按4个字节算，因此读取要多读一个寄存器地址
            nLen++;
        }

        // 响应报文长度
        int nExpectResponseLen;
        // 请求的报文
        byte[] byRequest;
        if (AttributeOperationEnum.READ.equals(operation)) {
            // 采集
            byRequest = new byte[8];
            byRequest[0] = (byte) nSlaveId;
            byRequest[1] = (byte) nFunCode;
            byRequest[2] = UtilFunctions.getHBYTE(nStart);
            byRequest[3] = UtilFunctions.getLBYTE(nStart);
            byRequest[4] = UtilFunctions.getHBYTE(nLen);
            byRequest[5] = UtilFunctions.getLBYTE(nLen);
            int nCheckCode = UtilFunctions.getCRC16(byRequest, 6);
            byRequest[6] = UtilFunctions.getLBYTE(nCheckCode);
            byRequest[7] = UtilFunctions.getHBYTE(nCheckCode);

            // 响应长度：头部3字节 + 数据部分长度 + CRC校验码2字节
            if (index >= 0 && index <= 1) {
                // 因为前两种寄存器是位寄存器，1字节8位。即使只获取一位，一次性至少会获取一个字节。 因此需要+7/8
                nExpectResponseLen = 3 + ((nLen + 7) / 8) + 2;
            } else {
                // 其他寄存器
                nExpectResponseLen = 3 + nLen * 2 + 2;
            }
        } else {
            // 控制
            // 目前makeFrame限制写操作不能打包成帧，即一帧里只有一个tag。
            // 将此处以FuncCode进行分类的改成使用寄存器类型。
            nExpectResponseLen = 8;
            switch (nFunCode) {
                case 0x05: {
                    int nVal = Integer.parseInt((String) actions.get(0).getValue());
                    byRequest = new byte[8];
                    byRequest[0] = (byte) nSlaveId;
                    byRequest[1] = (byte) nFunCode;
                    byRequest[2] = UtilFunctions.getHBYTE(nStart);
                    byRequest[3] = UtilFunctions.getLBYTE(nStart);
                    byRequest[4] = (nVal == 1) ? (byte) 0xFF : (byte) 0x00;
                    byRequest[5] = 0x00;
                    int nCheckCode = UtilFunctions.getCRC16(byRequest, 6);
                    byRequest[6] = UtilFunctions.getLBYTE(nCheckCode);
                    byRequest[7] = UtilFunctions.getHBYTE(nCheckCode);
                }
                break;
                case 0x06: {
                    int nVal = Integer.parseInt((String) actions.get(0).getValue());
                    byRequest = new byte[8];
                    byRequest[0] = (byte) nSlaveId;
                    byRequest[1] = (byte) nFunCode;
                    byRequest[2] = UtilFunctions.getHBYTE(nStart);
                    byRequest[3] = UtilFunctions.getLBYTE(nStart);
                    byRequest[4] = UtilFunctions.getHBYTE(nVal);
                    byRequest[5] = UtilFunctions.getLBYTE(nVal);
                    int nCheckCode = UtilFunctions.getCRC16(byRequest, 6);
                    byRequest[6] = UtilFunctions.getLBYTE(nCheckCode);
                    byRequest[7] = UtilFunctions.getHBYTE(nCheckCode);
                }
                break;
                case 0x0F: {
                    // Write Multiple Coils
                    // 一次性写入多个值，需要上层下发的帧中，tag的寄存器地址连续。
                    // 先按照上层一定会是连续设置多个，以nStart作为起始地址。
                    // 目前此函数未实际使用。
                    byRequest = new byte[7 + (actions.size() + 7) / 8 + 2];
                    byRequest[0] = (byte) nSlaveId;
                    byRequest[1] = (byte) nFunCode;
                    byRequest[2] = UtilFunctions.getHBYTE(nStart);              // 起始地址
                    byRequest[3] = UtilFunctions.getLBYTE(nStart);
                    byRequest[4] = UtilFunctions.getHBYTE(actions.size());   // 操作个数
                    byRequest[5] = UtilFunctions.getLBYTE(actions.size());
                    byRequest[6] = (byte) ((actions.size() + 7) / 8);      // 数据部分的长度（不含CRC16）
                    int idx = 7;
                    int count = 0;
                    for (TslAttributeAction it : actions) {
                        boolean bVal = Integer.parseInt((String) it.getValue()) > 0;
                        if (bVal) {
                            byRequest[idx + count / 8] |= nBitMask[count % 8];
                        }
                        count++;
                    }
                    int nCheckCode = UtilFunctions.getCRC16(byRequest, 7 + (actions.size() + 7) / 8);
                    idx = 7 + (actions.size() + 7) / 8;
                    byRequest[idx++] = UtilFunctions.getLBYTE(nCheckCode);
                    byRequest[idx] = UtilFunctions.getHBYTE(nCheckCode);
                }
                break;
                case 0x10: { // Write Multiple Registers
                    // 目前仅用于扩展寄存器的写操作。
                    byRequest = new byte[7 + nLen * 2 + 2];
                    byRequest[0] = (byte) nSlaveId;
                    byRequest[1] = (byte) nFunCode;
                    byRequest[2] = UtilFunctions.getHBYTE(nStart);              // 起始地址
                    byRequest[3] = UtilFunctions.getLBYTE(nStart);
                    // 扩展寄存器，一个变量占4字节，因此操作个数需要乘以2
                    byRequest[4] = UtilFunctions.getHBYTE(actions.size() * 2);   // 操作个数
                    byRequest[5] = UtilFunctions.getLBYTE(actions.size() * 2);

                    byRequest[6] = (byte) (nLen * 2);              // 数据部分的长度（不含CRC16）
                    for (TslAttributeAction it : actions) {
                        int nVal = Integer.parseInt((String) it.getValue());
                        int nIdx = 7 + (Integer.parseInt(it.getDriverFunctionParam().split(";")[0]) - nMinAddr) * 2;
                        if (index == 5) {
                            // 4字节 AB CD
                            byRequest[nIdx++] = (byte) (nVal >> 24);
                            byRequest[nIdx++] = (byte) (nVal >> 16);
                            byRequest[nIdx++] = (byte) (nVal >> 8);
                            byRequest[nIdx] = (byte) (nVal);
                        } else if (index == 7) {
                            // 4字节 CD AB
                            byRequest[nIdx++] = (byte) (nVal >> 8);
                            byRequest[nIdx++] = (byte) (nVal);
                            byRequest[nIdx++] = (byte) (nVal >> 24);
                            byRequest[nIdx] = (byte) (nVal >> 16);
                        }
                    }

                    int nCheckCode = UtilFunctions.getCRC16(byRequest, 7 + nLen * 2);
                    byRequest[7 + nLen * 2] = UtilFunctions.getLBYTE(nCheckCode);
                    byRequest[7 + nLen * 2 + 1] = UtilFunctions.getHBYTE(nCheckCode);
                }
                break;
                default:
                    log.error("错误的方法码 [{}]", nFunCode);
                    return new DriverResult(-1, "错误的方法码");
            }
        }


        // 发送并接收报文
        String strSend = DatatypeConverter.printHexBinary(byRequest);
        log.debug(ipAddr + " 发送数据: " + strSend);
        byte[] byResponse = tcpHandler.sendAndRecv(ipAddr, byRequest, 500);

        if (nExpectResponseLen > 0) {
            if (byResponse == null || byResponse.length < nExpectResponseLen) {
                log.error("devId:"+devId+" identifier:"+identifier+" registerType:"+registerType+" 接收数据为空或长度不足");
                //返回失败网关会重新调用这个方法，接收数据为空不做任何处理
                return DriverResult.SUCCESS;
            }
        }

        // 解析响应报文
        String strRecv = DatatypeConverter.printHexBinary(byResponse);
        log.debug("接收数据：" + strRecv);
        //noinspection StatementWithEmptyBody
        if (AttributeOperationEnum.READ.equals(operation)) {
            // 采集
            if (index >= 0 && index <= 1) { // 按位读取
                // 位寄存器
                for (TslAttributeAction it : actions) {
                    int nOffset = Integer.parseInt(it.getDriverFunctionParam().split(";")[0]) - nMinAddr;
                    int nVal = (byResponse[3 + (nOffset / 8)] & nBitMask[nOffset % 8]) == 0 ? 0 : 1;
                    it.setValue(String.valueOf(nVal));
                }
            } else if (index >= 2 && index <= 3) { // 按两字节读取 AB 范围-32768~32767
                // 普通寄存器
                for (TslAttributeAction it : actions) {
                    String[] split = it.getDriverFunctionParam().split(";");
                    String regAddr = split[0];
                    int nOffset = Integer.parseInt(regAddr) - nMinAddr;
                    int nVal = byResponse[3 + nOffset * 2];
                    nVal = (nVal << 8) | ((int) (byResponse[3 + nOffset * 2 + 1]) & 0xFF);
                    if (split.length > 1) {
                        BigDecimal zoomFactor = new BigDecimal(split[1]);
                        it.setValue(BigDecimal.valueOf(nVal).multiply(zoomFactor).setScale(4, RoundingMode.HALF_UP).toString());
                    } else {
                        it.setValue(String.valueOf(nVal));
                    }
                }
            } else if (index >= 4 && index <= 5) { // 按四字节读取 AB CD
                // 扩展寄存器
                for (TslAttributeAction it : actions) {
                    String[] split = it.getDriverFunctionParam().split(";");
                    String regAddr = split[0];
                    int nOffset = Integer.parseInt(regAddr) - nMinAddr;
                    int base = 3 + nOffset * 2;
                    int nVal = (byResponse[base] << 8) | (byResponse[base + 1] & 0xFF);  // AB
                    nVal = (nVal << 16) | (byResponse[base + 2] << 8 & 0xFF00) | (byResponse[base + 3] & 0xFF); // CD
                    if (split.length > 2 && split[2].equals(TYPE_FLOAT)) {
                        byte[] bytes = new byte[]{byResponse[base], byResponse[base + 1], byResponse[base + 2], byResponse[base + 3]};
                        BigDecimal zoomFactor = new BigDecimal(split[1]);
                        it.setValue(BigDecimal.valueOf(Utils.bytesToFloat(bytes)).multiply(zoomFactor).setScale(4, RoundingMode.HALF_UP).toString());
                    } else if (split.length > 1) {
                        BigDecimal zoomFactor = new BigDecimal(split[1]);
                        it.setValue(BigDecimal.valueOf(nVal).multiply(zoomFactor).setScale(4, RoundingMode.HALF_UP).toString());
                    } else {
                        it.setValue(String.valueOf(nVal));
                    }
                }
            } else if (index >= 6 && index <= 7) { // 按四字节读取 CD AB
                // 扩展寄存器（高低位反转）
                for (TslAttributeAction it : actions) {
                    String[] split = it.getDriverFunctionParam().split(";");
                    String regAddr = split[0];
                    int nOffset = Integer.parseInt(regAddr) - nMinAddr;
                    int base = 3 + nOffset * 2 + 2; // +2 先处理AB部分
                    int nVal = (byResponse[base] << 8) | (byResponse[base + 1] & 0xFF); // AB
                    nVal = (nVal << 16) | (byResponse[base - 2] << 8 & 0xFF00) | (byResponse[base - 1] & 0xFF); // CD
                    if (split.length > 2 && split[2].equals(TYPE_FLOAT)) {
                        byte[] bytes = new byte[]{byResponse[base], byResponse[base + 1], byResponse[base - 2], byResponse[base - 1]};
                        BigDecimal zoomFactor = new BigDecimal(split[1]);
                        it.setValue(BigDecimal.valueOf(Utils.bytesToFloat(bytes)).multiply(zoomFactor).setScale(4, RoundingMode.HALF_UP).toString());
                    } else if (split.length > 1) {
                        BigDecimal zoomFactor = new BigDecimal(split[1]);
                        it.setValue(BigDecimal.valueOf(nVal).multiply(zoomFactor).setScale(4, RoundingMode.HALF_UP).toString());
                    } else {
                        it.setValue(String.valueOf(nVal));
                    }
                }
            }
        } else {
            //控制
        }

        return DriverResult.SUCCESS;
    }

    @Override
    public DriverResult accessService(TslServiceAction tslServiceAction) {
        return DriverResult.NO_IMPLEMENT;
    }


    //获得功能码
    int GetFunctionCode(final String strRegName, AttributeOperationEnum nAccessMode) {
        switch (strRegName.charAt(0)) {
            case '0'://0xxxx 逻辑线圈
                return nAccessMode.equals(AttributeOperationEnum.READ) ? 0x1 : 0x5;
            case '1'://1xxxx 输入位寄存器
                return nAccessMode.equals(AttributeOperationEnum.READ) ? 0x2 : INVALIDATE_FUNCTION;
            case '3'://3xxxx 输入寄存器
            case '8'://8xxxx 扩展输入寄存器
            case 'A'://Axxxx 扩展输入寄存器（高低位反转）
                return nAccessMode.equals(AttributeOperationEnum.READ) ? 0x4 : INVALIDATE_FUNCTION;
            case '4'://4xxxx 保持寄存器
                return nAccessMode.equals(AttributeOperationEnum.READ) ? 0x3 : 0x6;
            case '9'://9xxxx 扩展保持寄存器
            case 'B'://Bxxxx 扩展保持寄存器（高低位反转）
                return nAccessMode.equals(AttributeOperationEnum.READ) ? 0x3 : 0x10;
            default:
                return INVALIDATE_FUNCTION; //Invalidate FunctionCode
        }
    }

    public static void main(String[] args) {
    }
}
